namespace Jsl.FxCop
{
    using System;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Parses IL and hold event information.
    /// </summary>
    public class EventInformation
    {
        private readonly MethodCall eventAddOrRemoveCall;
        private readonly EventNode eventMember;
        private readonly Method handler;
        private readonly bool isEventAdder;

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="EventInformation"/> class.
        /// </summary>
        /// <param name="eventAddOrRemoveCall">The event add or remove call.</param>
        /// <param name="eventMember">The event member.</param>
        /// <param name="handler">The event handler.</param>
        /// <param name="isEventAdder"><c>true</c> if method call was an event adder.</param>
        private EventInformation(MethodCall eventAddOrRemoveCall, EventNode eventMember, Method handler, bool isEventAdder)
        {
            this.eventAddOrRemoveCall = eventAddOrRemoveCall;
            this.eventMember = eventMember;
            this.handler = handler;
            this.isEventAdder = isEventAdder;
        }
        #endregion Constructors

        #region Public Properties
        /// <summary>
        /// Gets the event add or remove call.
        /// </summary>
        /// <value>The event add or remove call.</value>
        public MethodCall EventAddOrRemoveCall
        {
            get { return this.eventAddOrRemoveCall; }
        }

        /// <summary>
        /// Gets the event member.
        /// </summary>
        /// <value>The event member.</value>
        public EventNode EventMember
        {
            get { return this.eventMember; }
        }

        /// <summary>
        /// Gets the event handler.
        /// </summary>
        /// <value>The event handler.</value>
        public Method Handler
        {
            get { return this.handler; }
        } 

        /// <summary>
        /// Gets a value indicating whether the method call was an event adder.
        /// </summary>
        /// <value><c>true</c> if the method call was an event adder.</value>
        public bool IsEventAdder
        {
            get { return this.isEventAdder; }
        }
        #endregion Public Properties

        #region Public Methods
        /// <summary>
        /// Tries to create an <see cref="EventInformation"/>.
        /// </summary>
        /// <param name="method">The method.</param>
        /// <param name="eventAddOrRemoveCall">The possible event add or remove call.</param>
        /// <param name="eventInformation">The event information.</param>
        /// <returns><c>true</c> if an event was found.</returns>
        public static bool TryCreate(Method method, MethodCall eventAddOrRemoveCall, out EventInformation eventInformation)
        {
            if (method == null)
            {
                throw new ArgumentNullException("method");
            }

            if (eventAddOrRemoveCall == null)
            {
                throw new ArgumentNullException("eventAddOrRemoveCall");
            }

            if (eventAddOrRemoveCall.Operands.Count == 1)
            {
                var memberBinding = eventAddOrRemoveCall.Callee as MemberBinding;
                if (memberBinding != null)
                {
                    var calledMethod = memberBinding.BoundMember as Method;
                    if (calledMethod != null)
                    {
                        var eventNode = calledMethod.DeclaringMember as EventNode;
                        if (eventNode != null)
                        {
                            var constructHandler = eventAddOrRemoveCall.Operands[0] as Construct;
                            if (constructHandler != null)
                            {
                                if (constructHandler.Operands.Count == 2)
                                {
                                    var handlerExpression = constructHandler.Operands[1] as UnaryExpression;
                                    if (handlerExpression != null)
                                    {
                                        var handlerMemberBinding = handlerExpression.Operand as MemberBinding;
                                        if (handlerMemberBinding != null)
                                        {
                                            eventInformation = new EventInformation(
                                                eventAddOrRemoveCall,
                                                eventNode,
                                                handlerMemberBinding.BoundMember as Method,
                                                object.ReferenceEquals(calledMethod, eventNode.HandlerAdder));

                                            return true;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            eventInformation = null;
            return false;
        }
        #endregion Public Methods
    }
}
